import java.util.*;
import java.util.concurrent.*;

public class Update implements Runnable{

	private Connect c;
	private int number;
	private String team;
	private String side;
	private String otherside;
	private HashMap<String,String> server;
	private HashMap<String,String> player;
	private Vector<HashMap<String,String>> types;
	volatile Vector<Message> refereeMsg;
	volatile boolean refereeMsgExist;
	volatile Vector<Message> coachMsg;
	volatile boolean coachMsgExist;
	volatile Vector<Message> playerMsg;
	volatile boolean playerMsgExist;
	volatile HashMap<String,String> see;
	volatile HashMap<String,String> flags;
	volatile Vector<Member> teamMates;
	volatile Vector<Member> opponents;
	volatile Vector<Member> players;
	volatile Vector<Double> ball;
	volatile Vector<Double> goal;
	volatile Vector<Double> myGoal;
	volatile HashMap<String,String> body;
	volatile int globalTime;
	volatile boolean logicalTime;
	private Logger log;
	private boolean stop;
	volatile Model world;
	
	public Update(String teamName, String host, boolean goalie) {
		this.c = new Connect(host,6000);
		this.team = teamName;
		String msg = "(init " + this.team + " (version 15.0)";
		if (!goalie) {
			msg = msg + ")";
		} else {
			msg = msg + "(goalie))";
		}
		this.c.sendMessage(msg);
		msg = this.c.receiveMessage();
		//System.out.println(msg);
		msg = msg.replaceAll("\\(|\\)","");
		String[] params = msg.split(" ");
		if (params[1].equals("l")) {
			this.side = "left";
			this.otherside = "right";
		} else {
			this.side = "right";
			this.otherside = "left";
		}
		this.number = Integer.parseInt(params[2]);
		this.log = new Logger("player" + this.side + Integer.toString(this.number) + ".txt");
		msg = this.c.receiveMessage();
		this.server = this.hashString(msg);
		this.log.insert(msg);
		msg = this.c.receiveMessage();
		this.player = this.hashString(msg);
		this.log.insert(msg);
		int numberTypes = Integer.parseInt((String)this.player.get("player_types"));
		this.types = new Vector<HashMap<String,String>>();
		for (int i = 0; i < numberTypes; i++) {
			msg = this.c.receiveMessage();
			HashMap<String,String> map = this.hashString(msg);
			this.types.add(map);
		}
		this.stop = false;
		//this.x = 0.0;
		//this.y = 0.0;
		this.refereeMsg = new Vector<Message>();
		this.refereeMsgExist = false;
		this.coachMsg = new Vector<Message>();
		this.coachMsgExist = false;
		this.playerMsg = new Vector<Message>();
		this.playerMsgExist = false;
		this.globalTime = 0;
		this.logicalTime = true;
		this.teamMates = new Vector<Member>();
		this.opponents = new Vector<Member>();
		this.players = new Vector<Member>();
		this.ball = new Vector<Double>();
		this.goal = new Vector<Double>();
		this.myGoal = new Vector<Double>();
		this.see = new HashMap<String,String>();
		this.body = new HashMap<String,String>();
		this.world = new Model(this.side);
		this.flags = new HashMap<String,String>();
	}

	/*** Info methods ***/
	
	public int getNumber() {
		return this.number;
	}
	
	public String getSide() {
		return this.side;
	}
	
	public String getServerParam(String param) {
		return (String)this.server.get(param);
	}
	
	public String getPlayerParam(String param) {
		return (String)this.player.get(param);
	}
	
	public String getTypeParam(String param, int type) {
		HashMap<String,String> playerType = this.types.elementAt(type);
		String typeParam = (String)playerType.get(param);
		return typeParam;
	}
	
	public String getPlayerStat(String stat) {
		return this.body.get(stat);
	}
	
	public int getTime() {
		return this.globalTime;
	}
	
	public boolean nextRefereeMsg() {
		return this.refereeMsgExist;
	}
	
	public Message getRefereeMsg() {
		Message msg;
		if (this.refereeMsgExist) {
			msg = this.refereeMsg.elementAt(0);
			this.refereeMsg.remove(0);
			if (this.refereeMsg.size() == 0) {
				this.refereeMsgExist = false;
			}
		} else {
			msg = new Message(this.globalTime,-1,"Error: there is no referee message");
		}
		return msg;
	}
	
	public boolean nextCoachMsg() {
		return this.coachMsgExist;
	}
	
	public Message getCoachMsg() {
		Message msg;
		if (this.coachMsgExist) {
			msg = this.coachMsg.elementAt(0);
			this.coachMsg.remove(0);
			if (this.coachMsg.size() == 0) {
				this.coachMsgExist = false;
			}
		} else {
			msg = new Message(this.globalTime,-1,"Error: there is no coach message");
		}
		return msg;
	}
	
	public boolean nextPlayerMsg() {
		return this.playerMsgExist;
	}
	
	public Message getPlayerMsg() {
		Message msg;
		if (this.playerMsgExist) {
			msg = this.playerMsg.elementAt(0);
			this.playerMsg.remove(0);
			if (this.playerMsg.size() == 0) {
				this.playerMsgExist = false;
			}
		} else {
			msg = new Message(this.globalTime,-1,"Error: there is no player message");
		}
		return msg;
	}
	
	public int getNumberPlayerTypes() {
		return this.types.size();
	}
	
	public String[] getPlayerType() {
		String[] types = (String[]) this.types.toArray();
		return types;
	}
	
	public double getX() {
		return this.world.getX();
	}
	
	public double getY() {
		return this.world.getY();
	}
	
	public double getAngle() {
	//	return this.world.getTheta();
		//return this.turnCount;
		return this.world.getTheta();
	}
	
	public Vector<Member> getTeamMates() {
		return this.teamMates;
	}
	
	public Vector<Member> getOpponents() {
		return this.opponents;
	}
	
	public Vector<Member> getPlayers() {
		return this.players;
	}
	
	public Vector<Double> getBall() {
		return this.ball;
	}
	
	public Vector<Double> getMyGoal() {
		return this.myGoal;
	}
	
	public Vector<Double> getOpponentGoal() {
		return this.goal;
	}
	
	public ConcurrentHashMap<String, ConcurrentHashMap<String,Double>> getFlags(String flagType) {
		try {
			ConcurrentHashMap<String, ConcurrentHashMap<String,Double>> flagReturn = new ConcurrentHashMap<String, ConcurrentHashMap<String,Double>>();
			String sideTemp = "r";
			String opponentTemp = "l";
			if (this.side.equals("left")) {
				sideTemp = "l";
				opponentTemp = "r";
			}
			Set<String> s = this.flags.keySet();
			Iterator<String> i = s.iterator();
			while (i.hasNext()) {
				String item = i.next();
				String[] items = item.split(" ");
				switch (flagType.charAt(0)) {
					case 'm':
						switch (flagType.charAt(1)) {
							case 'g':
								if (items[1].equals("g") && items[2].equals(sideTemp)) {
									String datum = this.flags.get(item);
									String[] data = datum.split(" ");
									ConcurrentHashMap<String,Double> params = new ConcurrentHashMap<String,Double>();
									switch (data.length) {
										case 1:
											params.put("Direction", Double.parseDouble(data[0]));
											break;
										default:
											params.put("Distance", Double.parseDouble(data[0]));
											params.put("Direction", Double.parseDouble(data[1]));
											break;
									}
									flagReturn.put(item, params);
								}
								break;
							case 'p':
								if (items[1].equals("p") && items[2].equals(sideTemp)) {
									String datum = this.flags.get(item);
									String[] data = datum.split(" ");
									ConcurrentHashMap<String,Double> params = new ConcurrentHashMap<String,Double>();
									switch (data.length) {
										case 1:
											params.put("Direction", Double.parseDouble(data[0]));
											break;
										default:
											params.put("Distance", Double.parseDouble(data[0]));
											params.put("Direction", Double.parseDouble(data[1]));
											break;
									}
									flagReturn.put(item, params);
								}
								break;
						}
						break;
					case 'o':
						switch (flagType.charAt(1)) {
							case 'g':
								if (items[1].equals("g") && items[2].equals(opponentTemp)) {
									String datum = this.flags.get(item);
									String[] data = datum.split(" ");
									ConcurrentHashMap<String,Double> params = new ConcurrentHashMap<String,Double>();
									switch (data.length) {
										case 1:
											params.put("Direction", Double.parseDouble(data[0]));
											break;
										default:
											params.put("Distance", Double.parseDouble(data[0]));
											params.put("Direction", Double.parseDouble(data[1]));
											break;
									}
									flagReturn.put(item, params);
								}
								break;
							case 'p':
								if (items[1].equals("p") && items[2].equals(opponentTemp)) {
									String datum = this.flags.get(item);
									String[] data = datum.split(" ");
									ConcurrentHashMap<String,Double> params = new ConcurrentHashMap<String,Double>();
									switch (data.length) {
										case 1:
											params.put("Direction", Double.parseDouble(data[0]));
											break;
										default:
											params.put("Distance", Double.parseDouble(data[0]));
											params.put("Direction", Double.parseDouble(data[1]));
											break;
									}
									flagReturn.put(item, params);
								}
								break;
						}
						break;
					case 'f':
						switch (flagType.charAt(1)) {
							case 'b':
								if ((items.length > 3) && (items[1].equals("t") || items[1].equals("r") || items[1].equals("b") || items[1].equals("l"))) {
									String datum = this.flags.get(item);
									String[] data = datum.split(" ");
									ConcurrentHashMap<String,Double> params = new ConcurrentHashMap<String,Double>();
									switch (data.length) {
										case 1:
											params.put("Direction", Double.parseDouble(data[0]));
											break;
										default:
											params.put("Distance", Double.parseDouble(data[0]));
											params.put("Direction", Double.parseDouble(data[1]));
											break;
									}
									flagReturn.put(item, params);
								}
								break;
							case 'c':
								if (items[1].equals("c")) {
									String datum = this.flags.get(item);
									String[] data = datum.split(" ");
									ConcurrentHashMap<String,Double> params = new ConcurrentHashMap<String,Double>();
									switch (data.length) {
										case 1:
											params.put("Direction", Double.parseDouble(data[0]));
											break;
										default:
											params.put("Distance", Double.parseDouble(data[0]));
											params.put("Direction", Double.parseDouble(data[1]));
											break;
									}
									flagReturn.put(item, params);
								}
								break;
							case 't':
								if ((items.length == 3) && (items[1].equals("t") || items[1].equals("r") || items[1].equals("b") || items[1].equals("l"))) {
									String datum = this.flags.get(item);
									String[] data = datum.split(" ");
									ConcurrentHashMap<String,Double> params = new ConcurrentHashMap<String,Double>();
									switch (data.length) {
										case 1:
											params.put("Direction", Double.parseDouble(data[0]));
											break;
										default:
											params.put("Distance", Double.parseDouble(data[0]));
											params.put("Direction", Double.parseDouble(data[1]));
											break;
									}
									flagReturn.put(item, params);
								}
								break;
						}
						break;
				}
			}
			return flagReturn;
		} catch (Exception e) {
			return null;
		}
	}
	
	/*** Action methods ***/
	
	public void catchBall(int direction) { //between minmoment and maxmoment
		String msg = "(catch " + Integer.toString(direction) + ")";
		this.c.sendMessage(msg);
	}
	
	public boolean dash(int power) { //between minpower and maxpower
		boolean flag = true;
		if (this.logicalTime) {
			String msg = "(dash " + Integer.toString(power) + ")";
			this.c.sendMessage(msg);
			this.logicalTime = false;
		} else {
			flag = false;
		}
		return flag;
	}
	
	public void kick(int power, int direction) {
		String msg = "(kick " + Integer.toString(power) + " " + Integer.toString(direction) + ")";
		this.c.sendMessage(msg);
	}
	
	public boolean move(double x, double y) {
		boolean flag = true;
		if (this.logicalTime) {
			this.world.updateXY(x, y);
			String msg = "(move " + Double.toString(x) + " " + Double.toString(y) + ")";
			this.c.sendMessage(msg);
			this.logicalTime = false;
		} else {
			flag = false;
		}
		return flag;
	}
	
	public void say(String message) {
		String msg = "(say " + message + ")";
		this.c.sendMessage(msg);
	}
	
	public boolean turn(int moment) { //between minmoment and maxmoment
		boolean flag = true;
		if (this.logicalTime) {
			String msg = "(turn " + Integer.toString(moment) + ")";
			this.c.sendMessage(msg);
			this.logicalTime = false;
		} else {
			flag = false;
		}
		return flag;
	}
	
	public void turnNeck(int angle) { //between minneckmoment and maxneckmoment
		String msg = "(turn_neck " + Integer.toString(angle) + ")";
		this.c.sendMessage(msg);
	}
	
	public void disconnect() {
		this.stop = true;
		this.log.close();
		this.c.sendMessage("(bye)");
	}
	
	public void run() {
		while (!this.stop) {
			String msg = this.c.receiveMessage();
			this.logicalTime = true;
			this.updateData(msg);
			if (!this.stop) {
				this.log.insert(msg);
			}
		}
	}
	
	/*** Private methods ***/
		
	private HashMap<String,String> hashString(String params) {
		Parser p = new Parser(params);
		int time = Integer.parseInt(p.getTime());
		if (time > this.globalTime) {
			this.globalTime = time;
		}
		return p.getMap();
	}
	
	private String getMsgName(String str) {
		String temp = str.substring(1);
		return temp.substring(0,temp.indexOf(' '));
	}
	
	private void updateData(String serverMessage) {
		String msgType = this.getMsgName(serverMessage);
		if (msgType.equals("sense_body")) {
			this.body.clear();
			this.body = this.hashString(serverMessage);
		}
		if (msgType.equals("hear")) {
			String newMsg = serverMessage.substring(1,serverMessage.indexOf(')'));
			//System.out.println(newMsg);
			String[] data = newMsg.split(" ",4);
			if (data[2].equals("referee")) {
				Message msgFromReferee = new Message(Integer.parseInt(data[1]),0,data[3]);
				this.refereeMsg.add(msgFromReferee);
				this.refereeMsgExist = true;
			} else {
				if (data[2].equals("online_coach_" + this.side) || data[2].equals("coach")) {
					Message msgFromCoach = new Message(Integer.parseInt(data[1]),0,data[3]);
					this.coachMsg.add(msgFromCoach);
					this.coachMsgExist = true;
				} else {
					if (data[2].equals("online_coach_" + this.otherside)) {
						//New lines
					} else {
						if (!data[2].equals("self")) {
							Message msgFromOtherPlayer = new Message(Integer.parseInt(data[1]),Integer.parseInt(data[2]),data[3]);
							this.playerMsg.add(msgFromOtherPlayer);
							this.playerMsgExist = true;
						}
					}
				}
			}
		}
		if (msgType.equals("see")) {
			this.see.clear();
			this.teamMates.clear();
			this.opponents.clear();
			this.ball.clear();
			this.goal.clear();
			this.myGoal.clear();
			this.flags.clear();
			this.see = this.hashString(serverMessage);
			Set<String> seeHashMapKeySet = this.see.keySet();
			Iterator<String> iterator_seeHashMapKeySet = seeHashMapKeySet.iterator();
			while (iterator_seeHashMapKeySet.hasNext()) {
				String nextKey_seeHashMapKeySet = iterator_seeHashMapKeySet.next();
				String[] keyComponents = nextKey_seeHashMapKeySet.split(" ");
				if (keyComponents[0].equals("p")) {
					if (keyComponents.length > 2) {
						String data = this.see.get(nextKey_seeHashMapKeySet);
						Member newPlayer = new Member(Integer.parseInt(keyComponents[2]),false,data);
						if (keyComponents[1].equals("\"" + this.team + "\"")) {
							this.teamMates.add(newPlayer);
						} else {
							this.opponents.add(newPlayer);
						}
					} else {
						String data = this.see.get(nextKey_seeHashMapKeySet);
						Member newPlayer = new Member(-1,false,data);
						this.players.add(newPlayer);
					}
				}
				if (keyComponents[0].equals("b")) {
					String ballData = this.see.get(nextKey_seeHashMapKeySet);
					String[] singleBallDatum = ballData.split(" ");
					for (int j = 0; j < singleBallDatum.length; j++) {
						this.ball.add(Double.parseDouble(singleBallDatum[j]));
					}
				}
				if (keyComponents[0].equals("g")) {
					String goalieData = this.see.get(nextKey_seeHashMapKeySet);
					String[] singleGoalieDatum = goalieData.split(" ");
					for (int j = 0; j < singleGoalieDatum.length; j++) {
						if (keyComponents[1].equals("l")) {
							if (this.side.equals("left")) {
								this.myGoal.add(Double.parseDouble(singleGoalieDatum[j]));
							} else {
								this.goal.add(Double.parseDouble(singleGoalieDatum[j]));
							}
						} else {
							if (this.side.equals("left")) {
								this.goal.add(Double.parseDouble(singleGoalieDatum[j]));
							} else {
								this.myGoal.add(Double.parseDouble(singleGoalieDatum[j]));
							}
						}
					}
				}
				if (keyComponents[0].equals("f")) {
					if (keyComponents.length > 2) {
						//System.out.println("update: " + item + " => " + this.see.get(item));
						this.flags.put(nextKey_seeHashMapKeySet, this.see.get(nextKey_seeHashMapKeySet));
					}
					//System.out.println(Integer.toString(this.flags.size()));
				}
			}
			if (this.flags.size() > 1) {
				//System.out.println("updating");
				HashMap<String,String> temp = this.flags;
				this.world.updateWorld(temp);
			}
			//System.out.println("x = " + Double.toString(this.world.getX()));
		}
	}

}
